package middleware

import (
	"bytes"
	"illuminant/config"
	"illuminant/logger"
	"illuminant/middleware/auth"
	"illuminant/util"
	"io/ioutil"
	"strings"

	jwt "github.com/appleboy/gin-jwt/v2"
	"github.com/casbin/casbin/v2"
	"github.com/casbin/casbin/v2/model"
	"github.com/gin-gonic/gin"
)

// NewAuthorizer returns the authorizer, uses a Casbin enforcer as input
func NewAuthorizer() gin.HandlerFunc {
	cnf := config.Get()
	lg := logger.GetLogger()
	adp, err := auth.NewAdapter()
	if err != nil {
		lg.Err(err).Msg("casbin adapter error")
		panic(err)
	}

	m, err := model.NewModelFromString(cnf.Auth.RBACModel)
	if err != nil {
		lg.Err(err).Msg("casbin model from string error")
		panic(err)
	}

	e, err := casbin.NewEnforcer(m, adp)
	if err != nil {
		lg.Err(err).Msg("casbin enforcer error")
		panic(err)
	}

	a := &RBACAuthorizer{enforcer: e}
	return func(c *gin.Context) {
		if !a.CheckPermission(c) {
			a.RequirePermission(c)
			c.Abort()
		}
	}
}

// RBACAuthorizer stores the casbin handler
type RBACAuthorizer struct {
	enforcer *casbin.Enforcer
}

// CheckPermission checks the user/method/path combination from the request.
// Returns true (permission granted) or false (permission forbidden)
func (a *RBACAuthorizer) CheckPermission(c *gin.Context) bool {
	lg := logger.GetLogger()
	claims := jwt.ExtractClaims(c)

	method := c.Request.Method
	path := c.Request.URL.Path

	// a.ReloadPermissions()
	if strings.Index(path, "/api/v1/graphql") < 0 {
		return a.RestFullPermission(claims["id"].(string), path, method)
	}

	// graphql api
	body, err := c.GetRawData()
	if err != nil {
		lg.Err(err).Msg("get body raw data")
		return false
	}
	c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))

	return a.GraphqlPermission(claims["id"].(string), body)
}

// RequirePermission returns the 403 Forbidden to the client
func (a *RBACAuthorizer) RequirePermission(c *gin.Context) {
	util.Fail(c, util.AUTH_PERMISSION_DENIED, "权限不足", nil)
}

func (a *RBACAuthorizer) ReloadPermissions() {
	a.enforcer.LoadPolicy()
}

func (a *RBACAuthorizer) RestFullPermission(userId, path, method string) bool {
	lg := logger.GetLogger()

	allowed, err := a.enforcer.Enforce(userId, path, method)
	if err != nil {
		lg.Err(err).Msg("RestFullPermission check error")
		return false
	}

	return allowed
}

func (a *RBACAuthorizer) GraphqlPermission(userId string, body []byte) bool {
	lg := logger.GetLogger()

	funcs, err := util.GetGraphqlFunc(body)
	if err != nil {
		lg.Err(err).Msg("GetGraphqlFunc error")
		return false
	}

	for _, f := range funcs {
		lg.Debug().Str("userId", userId).Str("obj", f).Msg("check graphql permission")
		allowed, err := a.enforcer.Enforce(userId, f, "*")
		if err != nil {
			lg.Err(err).Msg("GraphqlPermission check error")
			return false
		}

		if !allowed {
			return allowed
		}
	}

	return true
}
